//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.TridentModel
{
    #region Using Directives
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Controls;
    using System.Windows.Media;
    using ErActivity = Microsoft.Research.DataLayer.Activity;
    using System.Workflow.ComponentModel;
    using Microsoft.Research.DataLayer;
    using System.Collections.ObjectModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using SR = Microsoft.Research.DataLayer;

    #endregion

    /// <summary>
    /// A data model representing a category for a workflow activity. This
    /// model contains the metadata associated with that category that is
    /// helpful for a workflow designer.
    /// </summary>
    [Serializable()]
    public class WorkflowCategoryModel
    {
        #region Private Member Data
        /// <summary>
        /// Name of the Category Model.
        /// </summary>
        private string name;

        /// <summary>
        /// Label of the Category Model.
        /// </summary>
        private string label;

        /// <summary>
        /// Id of the Category Model.
        /// </summary>
        private Guid id;

        /// <summary>
        /// Description of the category Model.
        /// </summary>
        private string description;

        /// <summary>
        /// Whether the category is editable or not.
        /// </summary>
        private bool isEditable = true;

        /// <summary>
        /// Edit Color.
        /// </summary>
        [NonSerialized]
        private Color editColor = Colors.Gray;

        /// <summary>
        /// Is edit color Default.
        /// </summary>
        private bool isEditColorDefault = true;

        /// <summary>
        /// Imgae source of the icon.
        /// </summary>
        [NonSerialized]
        private ImageSource iconImageSource;

        /// <summary>
        /// Parent category Model.
        /// </summary>
        private WorkflowCategoryModel parent;

        /// <summary>
        /// Sub category Models.
        /// </summary>
        private Collection<WorkflowCategoryModel> subcats = new Collection<WorkflowCategoryModel>();

        /// <summary>
        /// Activities under this Category Model.
        /// </summary>
        private Collection<WorkflowMetadata> workflows = new Collection<WorkflowMetadata>();
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="WorkflowCategoryModel"/> class.
        /// </summary>
        private WorkflowCategoryModel() { }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="category"></param>
        public WorkflowCategoryModel(Namespace category, bool showLabeledWorkflows)
        {
            this.name = category.Name;
            this.label = category.Label;
            this.id = ((IObject)category).ID;
            this.description = category.Description;
            this.isEditable = category.IsEditable;
            if (null != category.User)
            {
                this.CategoryUserId = (category.User as IObject).ID;
            }

            if (null != category.EditorColor)
            {
                this.editColor = Color.FromArgb(
                    category.EditorColor.A,
                    category.EditorColor.R,
                    category.EditorColor.G,
                    category.EditorColor.B);
            }

            // Add workflows corresponding to the model
            foreach (ErActivity workflow in category.Activitys)
            {
                if (workflow.IsWorkflow && !workflow.IsDeleted)
                {
                    bool addToTree = false;

                    // If no newer versions of the workflow exist then add it to the list.
                    workflow.NewerVersions.GetEnumerator();
                    if (workflow.NewerVersions.Count == 0)
                    {
                        addToTree = true;
                    }
                    else
                    {
                        // If workflow is labelled then add to the tree.
                        if (showLabeledWorkflows && !string.IsNullOrEmpty(workflow.VersionLabel))
                        {
                            addToTree = true;
                        }
                    }

                    if (addToTree)
                    {
                        addToTree = workflow.Security.Rights.AuthorWorkflow.Check(TridentAuthentication.LoggedInUser);
                    }

                    if (addToTree)
                    {
                        WorkflowMetadata wfMeta = new WorkflowMetadata(workflow);
                        if (null != wfMeta)
                        {
                            this.workflows.Add(wfMeta);
                        }

                        wfMeta.ParentCategoryId = (category as IObject).ID;
                        wfMeta.ParentCategoryName = category.Name;
                    }
                }
            }

            // Add the child category models to the current model.
            foreach (Namespace childNamespace in category.Children)
            {
                WorkflowCategoryModel childModel = new WorkflowCategoryModel(childNamespace, showLabeledWorkflows);

                // Add the child to the current model's sub categories.
                this.Categories.Add(childModel);
                childModel.parent = this;
            }
        }

        /// <summary>
        /// Create a Virtual category "Shared Workflows"
        /// </summary>
        /// <returns>WorkflowCategoryModel</returns>
        public static WorkflowCategoryModel CreateSharedWorkflows(Connection connection)
        {
            WorkflowCategoryModel model = new WorkflowCategoryModel();
            model.IsVirtualCategory = true;
            List<SR.Activity> allPermittedActivities = new List<SR.Activity>();
            try
            {
                model.Name = "Shared Workflows";
                model.Label = "Shared Workflows";
                model.Description = string.Empty;

                allPermittedActivities = SR.Activity.LoadAll(connection).Where(
                    activity => WorkflowCategoryModel.CheckActivityValidityForNormalUser(activity)).ToList();
                if (allPermittedActivities != null && allPermittedActivities.Count > 0)
                {
                    var userGroupedWfs = from wf in allPermittedActivities
                                         group wf by (wf.Owner as IObject).ID into wfGroup
                                         select new
                                         {
                                             SecurityPrincipal = new SecurityPrincipalModel(wfGroup.ElementAt(0).Owner),
                                             UserSharedWfs = wfGroup
                                         };

                    if (userGroupedWfs != null && userGroupedWfs.Count() > 0)
                    {
                        var groupedNonEmpty = userGroupedWfs.Where(userGrp => userGrp.UserSharedWfs != null && userGrp.UserSharedWfs.Count() > 0);
                        foreach (var grp in groupedNonEmpty)
                        {
                            WorkflowCategoryModel wfModel = new WorkflowCategoryModel();
                            wfModel.IsVirtualCategory = true;
                            wfModel.Name = grp.SecurityPrincipal.DisplayName;
                            wfModel.Label = grp.SecurityPrincipal.DisplayName;
                            foreach (var wf in grp.UserSharedWfs)
                            {
                                if (wfModel.Id.Equals(Guid.Empty))
                                {
                                    wfModel.id = WorkflowCategoryModel.FetchCategoryId(wf);
                                }

                                WorkflowMetadata wfMeta = new WorkflowMetadata(wf);
                                if (null != wfMeta)
                                {
                                    wfMeta.ParentCategoryId = wfModel.Id;
                                    wfMeta.ParentCategoryName = wfModel.name;
                                    wfModel.Workflows.Add(wfMeta);
                                }
                            }

                            model.Categories.Add(wfModel);
                        }
                    }
                }
                else
                {
                    model = null;
                }
            }
            catch (RecordNotFoundException) { }
            catch (Exception) { }

            return model;
        }

        /// <summary>
        /// Checks the activity validity.
        /// </summary>
        /// <param name="activity">The activity.</param>
        /// <returns></returns>
        private static bool CheckActivityValidityForNormalUser(ErActivity activity)
        {
            bool validActivity = false;
            if (activity != null && activity.Namespaces != null && activity.Namespaces.Count > 0)
            {
                if (!activity.IsDeleted
                    && activity.IsWorkflow
                    && TridentAuthentication.LoggedInUser != null
                    && !activity.Owner.Equals(TridentAuthentication.LoggedInUser)
                    && activity.Namespaces[0].Name.Equals("My Workflow", StringComparison.OrdinalIgnoreCase)
                    && activity.Namespaces[0].Parent.Name.Equals("UserWorkflowRoot", StringComparison.OrdinalIgnoreCase)
                    && (!string.IsNullOrEmpty(activity.VersionLabel) || activity.IsLatestVersion())
                    && activity.Security.Rights.AuthorWorkflow.Check(TridentAuthentication.LoggedInUser))
                {
                    validActivity = true;
                }
            }

            return validActivity;
        }

        /// <summary>
        /// Creates the virtual all user category.
        /// </summary>
        /// <param name="connection">The connection.</param>
        /// <returns></returns>
        public static WorkflowCategoryModel CreateVirtualAllUserCategory(Connection connection)
        {
            WorkflowCategoryModel model = new WorkflowCategoryModel();
            model.IsVirtualCategory = true;
            model.Name = "All users' Workflows";
            model.Label = "All users' Workflows";
            model.Description = string.Empty;

            try
            {
                List<User> allUsers = User.LoadAll(connection);
                Collection<Namespace> categories = new Collection<Namespace>();
                foreach (User user in allUsers)
                {
                    if ((user as IObject).ID != (TridentAuthentication.LoggedInUser as IObject).ID)
                    {
                        Namespace userRoot = user.WorkflowRoot;
                        if (userRoot != null)
                        {
                            userRoot.Refresh();
                            Namespace currentUserRoot = userRoot.Children.Where(
                                child => child.User != null).FirstOrDefault(
                                child => child.User.Equals(user));

                            if (currentUserRoot != null)
                            {
                                categories.Add(currentUserRoot);
                            }
                        }
                    }
                }

                if (categories.Count > 0 && categories.Any(category => category.Activitys.Where(activity => !activity.IsDeleted).Count() > 0))
                {
                    foreach (SR.Namespace category in categories)
                    {
                        if (category.Activitys.Where(activity => !activity.IsDeleted).Count() > 0)
                        {
                            WorkflowCategoryModel catModel = new WorkflowCategoryModel();
                            catModel.IsVirtualCategory = true;
                            catModel.Name = (new SecurityPrincipalModel(category.User as ISecurityPrincipal)).DisplayName;
                            model.Categories.Add(catModel);
                            foreach (SR.Activity activity in category.Activitys)
                            {
                                if (!activity.IsDeleted && (!string.IsNullOrEmpty(activity.VersionLabel) || activity.IsLatestVersion()))
                                {
                                    if (catModel.Id.Equals(Guid.Empty))
                                    {
                                        catModel.id = WorkflowCategoryModel.FetchCategoryId(activity);
                                    }

                                    WorkflowMetadata wfMeta = new WorkflowMetadata(activity);
                                    if (null != wfMeta)
                                    {
                                        wfMeta.ParentCategoryId = catModel.Id;
                                        wfMeta.ParentCategoryName = catModel.name;
                                        catModel.Workflows.Add(wfMeta);
                                    }
                                }
                            }
                        }
                    }
                }
                else
                {
                    model = null;
                }
            }
            catch (RecordNotFoundException) { }
            catch (Exception) { }

            return model;
        }

        /// <summary>
        /// Fetches the id for the category.
        /// </summary>
        /// <param name="activity"></param>
        /// <returns></returns>
        private static Guid FetchCategoryId(ErActivity activity)
        {
            Guid id = Guid.Empty;
            if (activity.Namespaces != null && activity.Namespaces.Count > 0)
            {
                id = (activity.Namespaces[0] as IObject).ID;
            }

            return id;
        }

        #region Properties

        /// <summary>
        /// A descriptive workflowName of the category. This should be a short but
        /// descriptive workflowName identifying the function of the workflows that
        /// fall in this category.
        /// </summary>
        public string Name
        {
            get
            {
                return this.name;
            }
            set
            {
                this.name = value;
            }
        }

        /// <summary>
        /// A descriptive workflowName of the category that is specifically shortened
        /// to fit all common uses inside a visual design tool. If the Label is
        /// not provided, the Name will be returned.
        /// </summary>
        public string Label
        {
            get
            {
                if (String.IsNullOrEmpty(label))
                    return this.name;
                return this.label;
            }
            set
            {
                this.label = value;
            }
        }

        /// <summary>
        /// A full description of the category.
        /// </summary>
        public string Description
        {
            get
            {
                return this.description;
            }
            set
            {
                this.description = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is editable.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is editable; otherwise, <c>false</c>.
        /// </value>
        public bool IsEditable
        {
            get
            {
                return this.isEditable;
            }
            set
            {
                this.isEditable = value;
            }
        }

        /// <summary>
        /// An identification for the Category.
        /// </summary>
        public Guid Id
        {
            get
            {
                return this.id;
            }
        }

        /// <summary>
        /// The parent category of this category. If this is a top level category, then
        /// null will be returned.
        /// </summary>
        public WorkflowCategoryModel Parent
        {
            get
            {
                return this.parent;
            }
        }

        /// <summary>
        /// Returns the root category above this category. If this is a top level category
        /// in the heirarchy, then this category is returned.
        /// </summary>
        public WorkflowCategoryModel RootCategory
        {
            get
            {
                if (this.parent == null)
                    return this;

                WorkflowCategoryModel ancestor = this.parent;
                while (ancestor.parent != null)
                    ancestor = ancestor.parent;

                return ancestor;
            }
        }

        /// <summary>
        /// The color used by the editor to give a visual clue about the category.
        /// The colors are arbitrary, but can be helpful for usability.
        /// </summary>
        public Color PrimaryEditorColor
        {
            get
            {
                if (isEditColorDefault && parent != null)
                {
                    return parent.PrimaryEditorColor;
                }
                return this.editColor;
            }
            set
            {
                isEditColorDefault = false;
                this.editColor = value;
            }
        }

        /// <summary>
        /// A lighter version of the PrimaryEditorColor
        /// </summary>
        public Color LightEditorColor
        {
            get
            {
                return Color.Multiply(PrimaryEditorColor, 0.3f);
            }
        }

        /// <summary>
        /// Gets or sets the ImageSource for an icon that symbolizes the function of this category. 
        /// If an image source is not specified, it will try and inherit that image source from 
        /// it's parent category. If there is no parent category and no image source has been 
        /// defined, then null is returned.
        /// </summary>
        public ImageSource IconImageSource
        {
            get
            {
                if (iconImageSource == null && Parent != null)
                {
                    return Parent.IconImageSource;
                }
                return this.iconImageSource;
            }
            set
            {
                this.iconImageSource = value;
            }
        }

        /// <summary>
        /// The children categories (ie subcategories) that fall under this category.
        /// </summary>
        public Collection<WorkflowCategoryModel> Categories
        {
            get
            {
                return this.subcats;
            }
        }

        /// <summary>
        /// Gets the workflows.
        /// </summary>
        /// <value>The workflows.</value>
        public Collection<WorkflowMetadata> Workflows
        {
            get
            {
                return this.workflows;
            }
        }

        /// <summary>
        /// Gets or sets the category user.
        /// </summary>
        /// <value>The category user.</value>
        public Guid CategoryUserId { get; set; }

        /// <summary>
        /// Gets or Sets a value that specifies if the category is virtual or not.
        /// </summary>
        public bool IsVirtualCategory { get; set; }

        #endregion
    }
}
